package neptune.manager.storage;


import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.StringWriter;
import java.util.ArrayList;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import neptune.manager.builder.MACAddressBuilder;
import neptune.manager.model.topology.Link;
import neptune.manager.model.topology.NetworkInterface;
import neptune.manager.model.topology.Node;
import neptune.manager.model.topology.Property;
import neptune.manager.model.topology.Software;
import neptune.manager.model.topology.Topology;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

public class XMLTemplateStorage implements TemplateStorage {
	
	
	private File templatesDir;

	/**
	 * @param templatesDir la directory che contiene i templates
	 */
	public XMLTemplateStorage(File templatesDir) {
		this.templatesDir = templatesDir;
	}

	@Override
	public String[][] getAllTemplate() throws StorageReadException 
	{
		if ( !templatesDir.isDirectory() )
			throw new StorageReadException("Unable to read directory "+templatesDir.getAbsolutePath() );
		File[] files = templatesDir.listFiles();
		// Filtro i file
		ArrayList<File> array=new ArrayList<File>();
		for (int i=0 ; i< files.length ; i++)
		{
			String nomeFile=files[i].getName();
			if ( !nomeFile.contains("~") && nomeFile.contains("xml"))
			{
				array.add(files[i]);
			}
		}
		String[][] result=new String[ array.size() ][3] ;
		for (int i=0 ; i< array.size() ; i++)
		{
			String name=array.get(i).getName();
			String[] dati=this.getDescriptionOfTemplate( name );
			result[i][0]=name;
			result[i][1]=dati[0];
			result[i][2]=dati[1];
		}
		return result;
	}
	
	
	/**
	 * Restituisce
	 * 
	 * [0]= id del template
	 * [1]= descrizione template
	 * 
	 * @param templateFileName
	 * @return
	 * @throws StorageReadException 
	 */
	private String[] getDescriptionOfTemplate(String templateFileName) throws StorageReadException 
	{
		DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
		docFactory.setValidating(false);
		// La validazione e' intesa solo in
		// riferimento ai DTD
		// TODO inserire riferimento a XML SCHEMA
		String file=this.templatesDir+File.separator+templateFileName;
		Document doc;
		try
		{
			String[] result= new String[2]; 
			doc = docFactory.newDocumentBuilder().parse(file);
			Element topologyEl = doc.getDocumentElement();
			result[0]=topologyEl.getAttribute("id");
			result[1]=topologyEl.getAttribute("description");
			return result;
		}
		catch (Exception e)
		{
			throw new StorageReadException(" Unable to read file "+file);
		}
		
	}
	


	@Override
	public String getXMLtemplate(String fileName) throws StorageReadException
	{	
		String pathFile=this.templatesDir+File.separator+fileName;
		DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
		docFactory.setValidating(false);
		// La validazione e' intesa solo in
		// riferimento ai DTD
		// TODO inserire riferimento a XML SCHEMA
		Document doc;
		try
		{
			doc = docFactory.newDocumentBuilder().parse(new File( pathFile ));
			Element topologyEl = doc.getDocumentElement();
			NodeList nodes = topologyEl.getElementsByTagName("interface");
			for (int i = 0 ; nodes!=null && i < nodes.getLength() ; i++)
			{
				Element interfaceEl = (Element)nodes.item(i);
				// genero i nuovi MAC Address
				interfaceEl.setAttribute("MAC", MACAddressBuilder.generateRandomMAC() );
			}
			// set up a transformer
			TransformerFactory transfac = TransformerFactory.newInstance();
			Transformer trans = transfac.newTransformer();
			trans.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no");
			trans.setOutputProperty(OutputKeys.INDENT, "yes");
			trans.setOutputProperty(OutputKeys.STANDALONE, "yes");
			trans.setOutputProperty( "{http://xml.apache.org/xslt}indent-amount", "2" );

			// create string from xml tree
			StringWriter sw = new StringWriter();
			StreamResult result = new StreamResult(sw);
			DOMSource source = new DOMSource(doc);
			trans.transform(source, result);
			return sw.toString();
		}
		catch (Exception e)
		{
			throw new StorageReadException(" Unable to read file ");
		}

	}

	@Override
	public void saveTemplate(Topology topology) throws StorageSaveException 
	{
		String xmlString = getXMLDescOfTopology(topology);
		try 
		{
			String pathFileDest=templatesDir.getAbsolutePath()+File.separator+System.currentTimeMillis()+".xml";
			File fileDest=new File( pathFileDest );
			fileDest.createNewFile();
			FileWriter writer = new FileWriter(fileDest);
			writer.write(xmlString);
			writer.flush();
		}
		catch (IOException e) 
		{
			throw new StorageSaveException(e);
		}
		
		
	}
	
	/**
	 * Restituisce la descrizione XML della topologia
	 * 
	 * @param topology
	 * @return
	 * @throws StorageSaveException
	 */
	private String getXMLDescOfTopology(Topology topology) throws StorageSaveException
	{
		DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
		docFactory.setValidating(false);

		try 
		{
			Document document = docFactory.newDocumentBuilder().newDocument();
			document.setXmlVersion("1.0");

			Element root = document.createElement("topology");
			root.setAttribute("id", topology.getName());
			root.setAttribute("description", topology.getDescription());
			document.appendChild(root);

			Element nodes = document.createElement("nodes");
			root.appendChild(nodes);
			Element links = document.createElement("links");
			root.appendChild(links);

			createNodesElement(document, topology, nodes);
			createLinksElement(document, topology, links);

			// ///////////////
			// Output the XML

			// set up a transformer
			TransformerFactory transfac = TransformerFactory.newInstance();
			Transformer trans = transfac.newTransformer();
			trans.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no");
			trans.setOutputProperty(OutputKeys.INDENT, "yes");
			trans.setOutputProperty(OutputKeys.STANDALONE, "yes");
			trans.setOutputProperty(
					"{http://xml.apache.org/xslt}indent-amount", "2");

			// create string from xml tree
			StringWriter sw = new StringWriter();
			StreamResult result = new StreamResult(sw);
			DOMSource source = new DOMSource(document);
			trans.transform(source, result);
			return sw.toString();

		} catch (ParserConfigurationException e) {
			throw new StorageSaveException(e);
		} catch (TransformerException e) {
			throw new StorageSaveException(e);
		}
	}
	
	/**
	 * 
	 * 
	 * @param doc
	 * @param topology
	 * @param nodesEl
	 */
	private static void createNodesElement(Document doc, Topology topology,
			Element nodesEl) {
		for (Node node : topology.getNodes())
		{
			Element nodeEl = doc.createElement("node");
			nodeEl.setAttribute("id", node.getId());
			nodesEl.appendChild(nodeEl);

			Element description = doc.createElement("description");
			description.setTextContent(node.getDescription());
			nodeEl.appendChild(description);
			
			
			Element name = doc.createElement("name");
			name.setTextContent(node.getName());
			nodeEl.appendChild(name);

			if (node.getCpu() != null) {
				Element cpu = doc.createElement("cpu");
				cpu.setAttribute("name", node.getCpu().getName());
				cpu.setAttribute("type", node.getCpu().getType());
				cpu.setAttribute("clock", Integer.toString(node.getCpu()
						.getClock()));
				cpu.setAttribute("amount", Integer.toString(node.getCpu().getAmount() ) );
				nodeEl.appendChild(cpu);
			}

			Element ram = doc.createElement("ram");
			ram.setAttribute("dimension", Integer.toString(node.getRam()));
			nodeEl.appendChild(ram);

			Element hd = doc.createElement("hd");
			hd.setAttribute("dimension", Float.toString(node.getHd()));
			nodeEl.appendChild(hd);

			Element networkIfs = doc.createElement("network_interfaces");
			nodeEl.appendChild(networkIfs);

			for (NetworkInterface netIf : node.getInterfaces()) {
				if (netIf == null)
					break;

				Element netIfEl = doc.createElement("interface");
				networkIfs.appendChild(netIfEl);
				netIfEl.setAttribute("name", netIf.getInterfaceName());
				netIfEl.setAttribute("MAC",
						netIf.getMacAddress() != null ? netIf.getMacAddress()
								.getAddress() : null);
				netIfEl.setAttribute("type", netIf.getType());

				Element ifBinding = doc.createElement("interface_binding");
				ifBinding.setAttribute("type", netIf.getBindingDescription());
				netIfEl.appendChild(ifBinding);
				/*
				for (IPAddress ipAddr : netIf.getIpAddresses())
				{
					Element ipAddrEl = doc.createElement("ip");
					ipAddrEl.setAttribute("version", Integer.toString(ipAddr
							.getVersion()));
					ipAddrEl.setAttribute("address", ipAddr.getAddress());
					ipAddrEl.setAttribute("subnetMask", ipAddr.getSubnetMask());
					ipAddrEl.setAttribute("type",
							ipAddr.isControlAddress() ? "control" : "topology");
					netIfEl.appendChild(ipAddrEl);
				}
				*/
			}

			if (node.getOperativeSystem() != null) {
				Element os = doc.createElement("operative_system");
				os.setAttribute("name", node.getOperativeSystem().getName());
				os.setAttribute("version", node.getOperativeSystem()
						.getVersion());
				os.setAttribute("username", node.getOperativeSystem()
						.getAdminUsername());
				os.setAttribute("password", node.getOperativeSystem()
						.getAdminPassword());
				nodeEl.appendChild(os);
			}

			Element softConf = doc.createElement("software_configuration");
			softConf.setAttribute("template", node.getSoftwareTemplate());
			nodeEl.appendChild(softConf);

			Element sshElement = doc.createElement("sshServer");
			sshElement.setAttribute("port", Integer.toString(node
					.getControlPort()));
			softConf.appendChild(sshElement);

			for (Software soft : node.getInstalledSoftwares()) {
				Element softEl = doc.createElement("software");
				softEl.setAttribute("name", soft.getId());
				softEl.setAttribute("type", soft.getType());
				softEl.setAttribute("version", soft.getVersion());
				softConf.appendChild(softEl);
			}

			if (node.getHypervisor() != null) {
				Element hypervisor = doc.createElement("hypervisor");
				hypervisor.setAttribute("name", node.getHypervisor().getType());
				hypervisor.setAttribute("version", node.getHypervisor()
						.getVersion());
				nodeEl.appendChild(hypervisor);
			}
		}
	}
	
	private static void createLinksElement(Document doc, Topology topology,
			Element links) {
		for (Link link : topology.getLinks()) {
			Element linkEl = doc.createElement("link");
			linkEl.setAttribute("id", link.getId());
			linkEl.setAttribute("type", link.isFullDuplex() ? "fullDuplex"
					: "halfDuplex");
			links.appendChild(linkEl);

			// SOURCE EP
			Element ep1 = doc.createElement("end_point");
			ep1.setAttribute("nodeId", link.getSource().getNodeId());
			ep1.setAttribute("type", "source");
			linkEl.appendChild(ep1);

			Element multipTech1 = doc.createElement("multiplexing");
			multipTech1.setAttribute("tech", Link.getMultiplexingTechLabel(link
					.getSource().getMultiplexingTech()));
			ep1.appendChild(multipTech1);

			Element epInterfaceEl1 = doc.createElement("interface");
			epInterfaceEl1.setAttribute("name", link.getSource()
					.getInterfaceId());
			ep1.appendChild(epInterfaceEl1);

//			if (link.getSource() instanceof OLPIEndPoint) {
//				Element epAddress = doc.createElement("macAddress");
//				epAddress.setAttribute("value", (((OLPIEndPoint) link
//						.getSource()).getAddress()).getAddress());
//				ep1.appendChild(epAddress);
//			}

			// DESTINATION EP
			Element ep2 = doc.createElement("end_point");
			ep2.setAttribute("nodeId", link.getDestination().getNodeId());
			ep2.setAttribute("type", "destination");
			linkEl.appendChild(ep2);

			Element multipTech2 = doc.createElement("multiplexing");
			multipTech2.setAttribute("tech", Link.getMultiplexingTechLabel(link
					.getDestination().getMultiplexingTech()));
			ep2.appendChild(multipTech2);

			Element epInterfaceEl2 = doc.createElement("interface");
			epInterfaceEl2.setAttribute("name", link.getDestination()
					.getInterfaceId());
			ep2.appendChild(epInterfaceEl2);

//			if (link.getSource() instanceof OLPIEndPoint) {
//				Element epAddress = doc.createElement("macAddress");
//				epAddress.setAttribute("value", (((OLPIEndPoint) link
//						.getDestination()).getAddress()).getAddress());
//				ep2.appendChild(epAddress);
//			}

			// Props
			for (Property prop : link.getProperties()) {
				if (prop != null) {
					Element propEl = doc.createElement("property");
					propEl.setAttribute("name", prop.getName());
					propEl.setAttribute("value", prop.getValue());
					linkEl.appendChild(propEl);
				}
			}
		}
	}

	@Override
	public void deleteTemplate(String nomeFile) throws StorageSaveException 
	{
		String pathFile=templatesDir.getAbsolutePath()+File.separator+nomeFile;
		File fileDest=new File( pathFile );
		boolean isDeleted = fileDest.delete();
		if (!isDeleted)
		{
			throw new StorageSaveException("Unable to remove template "+pathFile);
		}
	}

}
